package com.xf.service.impl;

import cn.hutool.core.date.DateUtil;
import com.xf.entity.AjaxResult;
import com.xf.entity.ExcelImport;
import com.xf.entity.Student;
import com.xf.enums.ImportSudentEnum;
import com.xf.service.ExcelPoi;
import com.xf.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * @author fengjinhao
 * @date 2021/9/18 13:36
 */
@Slf4j
@Service
public class ExcelPoiImpl implements ExcelPoi {
    /**
     * xls文件正则
     */
    private final static String XLS_RGE = "^.+\\.(?i)(xls)$";

    /**
     * xlsx正则
     */
    private final static String XLSX_RGE = "^.+\\.(?i)(xlsx)$";

    /**
     * 学生导入模板
     */
    private final static List<String> STUDENT= ImportSudentEnum.getEnumList();

    /**
     * 学生处理方法
     */
    @Autowired
    private StudentService studentService;

    @Override
    public AjaxResult importStudent(MultipartFile file) throws IOException {
        AjaxResult ajaxResult = new AjaxResult();
        // 获取当前时间
        long l = System.currentTimeMillis();

        // 获取文件名
        String fileName = file.getOriginalFilename();

        // 校验文件格式
        if (!fileName.matches(XLS_RGE) && !fileName.matches(XLSX_RGE)) {
            return new AjaxResult("上传文件格式错误,仅限xls/xlsx").success();
        }
        // 获取文件流
        InputStream inputStream = file.getInputStream();
        //根据不同的文件生成不同的对象
        Workbook wb = null;
        if (!fileName.matches(XLSX_RGE)) {
            wb = new HSSFWorkbook(inputStream);
        }
        if (fileName.matches(XLSX_RGE)) {
            wb = new XSSFWorkbook(inputStream);
        }
        // 获取第1个sheet
        Sheet sheet = wb.getSheetAt(0);
        ajaxResult = commonHandlerData(sheet, STUDENT);

        int nbThreads = Thread.getAllStackTraces().keySet().size();
        log.info(fileName + "导入数据成功，耗时" + (System.currentTimeMillis() - l) + "毫秒,当前线程数为：" + nbThreads);
        return ajaxResult;
    }


    /**
     * @param sheet          excel
     * @param mbTableHeaders 导入模板类
     * @return
     */
    public AjaxResult commonHandlerData(Sheet sheet, List<String> mbTableHeaders) {
        // 数据导入请求结果
        ExcelImport excelImport = new ExcelImport();
        // 存放导入失败的数据行号
        List<Integer> failHh = new CopyOnWriteArrayList<>();
        // 表数据转为集合list
        List<Map<String, Object>> totalData = new CopyOnWriteArrayList<>();

        // 记录表头字段对应的列序号
        Map<String, Integer> nameAndIndex = new ConcurrentHashMap<>();
        // 导入失败行以及对应原因记录
        Map<Integer, Object> hhAndMsg = new ConcurrentHashMap<>();


        // 表头验证是否合格
        boolean spin = false;
        // 表头检验容错行数默认10行
        int rcSize = 10;
        // 记录首行有效数据所在行数
        int btNum = 0;
        // 表头检验容错次数 默认10次
        for (int r = 0; r < rcSize; r++) {
            btNum = r;
            Row row = sheet.getRow(r);
            if (row == null) {
                continue;
            }

            // 校验表格是否复核入库规范
            // 获取file文件的表头
            List<String> tableHeaders = new ArrayList<>();
            for (int i = 0; i < row.getLastCellNum(); i++) {
                if (Objects.nonNull(row.getCell(i))) {
                    row.getCell(i).setCellType(CellType.STRING);
                    String tableHeader = row.getCell(i).getStringCellValue();
                    if (StringUtils.isNotEmpty(tableHeader)) {
                        tableHeaders.add(tableHeader);
                        nameAndIndex.put(tableHeader, i);
                    }
                }
            }
            boolean compareResult = tableHeaders.stream().sorted().collect(Collectors.joining())
                    .equals(mbTableHeaders.stream().sorted().collect(Collectors.joining()));
            if (compareResult) {
                spin = true;
                break;
            }
        }
        // 超过容错行还没有匹配成功直接认为模板不匹配
        if (!spin) {
            return new AjaxResult("excel模板规格校验失败").success();
        }
        // 匹配成功,并发处理数据（以行为单位）
        CountDownLatch countDownLatch = new CountDownLatch(sheet.getLastRowNum() - btNum);
        // 默认核心线程数以及最大线程数
        int hexinSize = 5;
        int maxSize = 10;
        // 数据量过大，动态设置核心线程数以及最大并发数
        if (sheet.getLastRowNum() > 100) {
            hexinSize = sheet.getLastRowNum() / 20;
            maxSize = sheet.getLastRowNum() / 5;
        }

        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(hexinSize, maxSize, 0L, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>(sheet.getLastRowNum()));

        // 开始处理数据
        for (int r = btNum + 1; r <= sheet.getLastRowNum(); r++) {
            Row row = sheet.getRow(r);
            if (row == null) {
                countDownLatch.countDown();
                continue;
            }

            int finalR = r;
            threadPoolExecutor.execute(() -> {
                importSpData(row, nameAndIndex,totalData,hhAndMsg, failHh, countDownLatch, finalR,
                        mbTableHeaders);
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            return new AjaxResult("线程中断异常，请重新导入").success();
        }
        excelImport.setTotal(totalData.size() + failHh.size());
        excelImport.setSuccessSize(totalData.size());
        excelImport.setFailSize(failHh.size());
        excelImport.setFailHh(failHh);
        excelImport.setFailMsg(hhAndMsg);
        // 处理数据入库
        importStudentMysql(totalData);
        if (failHh.size() > 0) {
            return new AjaxResult("数据部分导入失败:" + excelImport.getFailMsg().values()).success();
        }
        return new AjaxResult("数据成功导入").success();
    }

    /**
     *
     * @param failHh         错误发生所在行
     * @param countDownLatch countDownLatch
     * @param finalR         当前操作行
     */
    private void importSpData(Row row, Map<String, Integer> nameAndIndex,List<Map<String, Object>> totalData,
                              Map<Integer, Object> hhAndMsg, List<Integer> failHh, CountDownLatch countDownLatch,
                              int finalR, List<String> mbTableHeaders){
        try {
            // 记录表字段和value:行数据转map
            Map<String, Object> nameAndValue = new HashMap<>();
            // 检验代码暂时注释
            StringBuffer msgBuffer = new StringBuffer();
            mbTableHeaders.forEach(headName -> {
                if (Objects.isNull(row.getCell(nameAndIndex.get(headName)))) {
                    nameAndValue.put(ImportSudentEnum.getValueByKey(headName), null);
                } else {
                    row.getCell(nameAndIndex.get(headName)).setCellType(CellType.STRING);
                    nameAndValue.put(ImportSudentEnum.getValueByKey(headName),
                            row.getCell(nameAndIndex.get(headName), Row.MissingCellPolicy.RETURN_NULL_AND_BLANK).getStringCellValue());
                }
                String string = Objects.isNull(nameAndValue.get(ImportSudentEnum.getValueByKey(headName))) ? "" :
                        nameAndValue.get(ImportSudentEnum.getValueByKey(headName)).toString();
                String grepByKey = ImportSudentEnum.getGrepByKey(headName);
                if (!string.matches(grepByKey)) {
                    msgBuffer.append(ImportSudentEnum.getMsgByKey(headName) + ",");
                    nameAndValue.remove(ImportSudentEnum.getValueByKey(headName));
                }
            });
            if (nameAndValue.size() != mbTableHeaders.size()) {
                hhAndMsg.put(finalR, "第" + finalR + "行数据检验失败原因:" + msgBuffer.substring(0, msgBuffer.length() - 1));
                failHh.add(hhAndMsg.size());
                return;
            }

            // 当前时间
            nameAndValue.put("creatTime",DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));

            // 单个添加
/*            // 获取模板里的姓名
            String name = nameAndValue.get(ImportSudentEnum.NAME.getValue()).toString();
            // 获取年龄
            Integer age = (Integer) nameAndValue.get(ImportSudentEnum.AGE.getValue());
            Student student = new Student();
            student.setName(name);
            student.setAge(age);
            student.setCreatTime(new Date());
            studentService.save(student);*/

            // 批量添加封装到totalData里面
            totalData.add(nameAndValue);
        }catch (Exception e) {
            log.error("数据导入存在异常 异常数据所在行为第:{} {}", finalR + "行", e.getStackTrace());
            hhAndMsg.put(finalR, "第" + finalR + "行数据导入异常:" + e.getMessage());
            failHh.add(finalR);
        } finally {
            countDownLatch.countDown();
        }
    }

    /**
     * 学生数据入库
     *
     * @param totalData 最终入库数据
     */
    private void importStudentMysql(List<Map<String, Object>> totalData) {
        log.info(totalData.toString());
        studentService.importStudentInfoData(totalData);
    }
}
